﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Upreader.Application;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Upreader.Application.Model;
using System.Diagnostics;

namespace Upreader.Engine
{
    /// <summary>
    /// Helper class for loading server processors
    /// </summary>
    class ServerLoader
    {
        #region Fields

        private readonly DownloadEngineContext _context;

        #endregion // Fields

        #region Constructor

        public ServerLoader(DownloadEngineContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            _context = context;
        }

        #endregion // Constructor

        #region Methods

        public Task Run(CancellationToken cancellationToken)
        {
            TaskCompletionSource<object> resultCompletionSource = new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);

            // create a _downloadSegmentProcessorHandler in the current fileContext responsible for running when something changes to  the servers
            ModelsChangedEventHandler eventHandler = (IApplicationService sender, ModelsChangedEventArgs e) =>
            {
                // start a new thread since we dont want to block the event source
                Task.Factory.StartNew(() =>
                {
                    // loop over each entry and perform the nessesary action
                    foreach (var entry in e.Entries)
                    {
                        switch (entry.State)
                        {
                            case ModelState.Added:
                                {
                                    Server changedServer = _context.Service.GetServers(x => x.Id == entry.Id).Single();
                                    Add(changedServer);
                                }
                                break;
                            case ModelState.Removed:
                                Remove(entry.Id);
                                break;
                            case ModelState.Updated:
                                {
                                    Server changedServer = _context.Service.GetServers(x => x.Id == entry.Id).Single();
                                    Update(changedServer);
                                }
                                break;
                        }
                    }
                }, cancellationToken);
            };

            // register an event that is called when some server actions occour
            _context.Service.ServersChanged += new ModelsChangedEventHandler(eventHandler);

            cancellationToken.Register(() =>
            {
                // unregister from change notification
                _context.Service.ServersChanged -= eventHandler;

                foreach (var entry in _context.ServerDictionary)
                {
                    entry.Value.Close();
                }

                resultCompletionSource.TrySetCanceled();
            });

            // Get a set of existing servers
            var servers = _context.Service.GetServers();

            foreach (Server server in servers)
            {
                Add(server);
            }

            return resultCompletionSource.Task;
        }

        #endregion // Methods

        #region Helpers

        private void Remove(int serverId)
        {
            ServerProcessor serverProcessor;

            // todo: what if we cant find it?
            if (_context.ServerDictionary.TryRemove(serverId, out serverProcessor))
            {
                Trace.WriteLine(string.Format("{0} was removed", serverProcessor.ServerInformation.Title), Resources.TraceCategory_Server);

                serverProcessor.Dispose();

                EventHandler<ServerProcessorEventArgs> eventHandler = ServerProcessorRemoved;
                if (eventHandler != null)
                    eventHandler(this, new ServerProcessorEventArgs(serverProcessor));
            }
        }

        private void Add(Server server)
        {
            ServerProcessor processor = new ServerProcessor(server, _context);

            _context.ServerDictionary.TryAdd(server.Id, processor);

            Trace.WriteLine(string.Format("{0} was added with {1} connections", server.Title, server.ConnectionCount), Resources.TraceCategory_Server);

            EventHandler<ServerProcessorEventArgs> eventHandler = ServerProcessorAdded;
            if (eventHandler != null)
                eventHandler(this, new ServerProcessorEventArgs(processor));

            // todo: handle return value?
        }

        private void Update(Server server)
        {
            ServerProcessor processor;

            if (_context.ServerDictionary.TryGetValue(server.Id, out processor))
            {
                processor.UpdateServerInformation(server);

                Trace.WriteLine(string.Format("{0} was updated", server.Title), Resources.TraceCategory_Server);
            }
        }

        #endregion // Helpers

        #region Events

        /// <summary>
        /// Event raised when a server got added
        /// </summary>
        public event EventHandler<ServerProcessorEventArgs> ServerProcessorAdded;

        /// <summary>
        /// Event raised when a server downloadSegmentContext got removed
        /// </summary>
        public event EventHandler<ServerProcessorEventArgs> ServerProcessorRemoved;

        #endregion // Events
    }
}
